//
// experimental/detail/channel_operation.hpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef ASIO_EXPERIMENTAL_DETAIL_CHANNEL_OPERATION_HPP
#define ASIO_EXPERIMENTAL_DETAIL_CHANNEL_OPERATION_HPP

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)

#include "asio/detail/config.hpp"
#include "asio/associated_allocator.hpp"
#include "asio/associated_executor.hpp"
#include "asio/detail/op_queue.hpp"
#include "asio/execution/executor.hpp"
#include "asio/execution/outstanding_work.hpp"
#include "asio/executor_work_guard.hpp"
#include "asio/prefer.hpp"

#include "asio/detail/push_options.hpp"

namespace asio {
    namespace experimental {
        namespace detail {

// Base class for all channel operations. A function pointer is used instead of
// virtual functions to avoid the associated overhead.
            class channel_operation ASIO_INHERIT_TRACKED_HANDLER
                    {
                            public:
                            template <typename Executor, typename = void>
                            class handler_work_base;

                            template <typename Handler, typename IoExecutor, typename = void>
                            class handler_work;

                            void destroy()
                            {
                                func_(this, destroy_op, 0);
                            }

                            protected:
                            enum action
                            {
                                destroy_op = 0,
                                complete_op = 1,
                                cancel_op = 2,
                                close_op = 3
                            };

                            typedef void (*func_type)(channel_operation*, action, void*);

                            channel_operation(func_type func)
                            : next_(0),
                            func_(func),
                            cancellation_key_(0)
                            {
                            }

                            // Prevents deletion through this type.
                            ~channel_operation()
                            {
                            }

                            friend class asio::detail::op_queue_access;
                            channel_operation* next_;
                            func_type func_;

                            public:
                            // The operation key used for targeted cancellation.
                            void* cancellation_key_;
                    };

            template<typename Executor, typename>
            class channel_operation::handler_work_base {
            public:
                handler_work_base(int, const Executor &ex)
                        : executor_(asio::prefer(ex, execution::outstanding_work.tracked)) {
                }

                template<typename Function, typename Handler>
                void post(Function &function, Handler &handler) {
                    typename associated_allocator<Handler>::type allocator =
                            (get_associated_allocator)(handler);

                    execution::execute(
                            asio::prefer(
                                    asio::require(executor_, execution::blocking.never),
                                    execution::allocator(allocator)),
                            ASIO_MOVE_CAST(Function)(function));
                }

            private:
                typename decay<
                        typename prefer_result<Executor,
                                execution::outstanding_work_t::tracked_t
                        >::type
                >::type executor_;
            };

#if !defined(ASIO_NO_TS_EXECUTORS)

            template<typename Executor>
            class channel_operation::handler_work_base<Executor,
                    typename enable_if<
                            !execution::is_executor<Executor>::value
                    >::type> {
            public:
                handler_work_base(int, const Executor &ex)
                        : work_(ex) {
                }

                template<typename Function, typename Handler>
                void post(Function &function, Handler &handler) {
                    typename associated_allocator<Handler>::type allocator =
                            (get_associated_allocator)(handler);

                    work_.get_executor().post(
                            ASIO_MOVE_CAST(Function)(function), allocator);
                }

            private:
                executor_work_guard <Executor> work_;
            };

#endif // !defined(ASIO_NO_TS_EXECUTORS)

            template<typename Handler, typename IoExecutor, typename>
            class channel_operation::handler_work :
                    channel_operation::handler_work_base<IoExecutor>,
                    channel_operation::handler_work_base<
                            typename associated_executor<Handler, IoExecutor>::type, IoExecutor> {
            public:
                typedef channel_operation::handler_work_base<IoExecutor> base1_type;

                typedef channel_operation::handler_work_base<
                        typename associated_executor<Handler, IoExecutor>::type, IoExecutor>
                        base2_type;

                handler_work(Handler &handler, const IoExecutor &io_ex)

                ASIO_NOEXCEPT
                        : base1_type(0, io_ex),
                        base2_type(
                0, (get_associated_executor)(handler, io_ex))
                {
                }

                template<typename Function>
                void complete(Function &function, Handler &handler) {
                    base2_type::post(function, handler);
                }
            };

            template<typename Handler, typename IoExecutor>
            class channel_operation::handler_work<
                    Handler, IoExecutor,
                    typename enable_if<
                            is_same<
                                    typename associated_executor<Handler,
                                            IoExecutor>::asio_associated_executor_is_unspecialised,
                                    void
                            >::value
                    >::type> : handler_work_base<IoExecutor> {
            public:
                typedef channel_operation::handler_work_base<IoExecutor> base1_type;

                handler_work(Handler &, const IoExecutor &io_ex)

                ASIO_NOEXCEPT
                        : base1_type(0, io_ex)
                        {
                        }

                template<typename Function>
                void complete(Function &function, Handler &handler) {
                    base1_type::post(function, handler);
                }
            };

        } // namespace detail
    } // namespace experimental
} // namespace asio

#include "asio/detail/pop_options.hpp"

#endif // ASIO_EXPERIMENTAL_DETAIL_CHANNEL_OPERATION_HPP
